/**
 * Copyright @2019 Josin All Rights Reserved.
 * Author: Josin
 * Email : xeapplee@gmail.com
 */
#include <fc_xml.h>
#include <fc_stack.h>
#include <fc_string.h>
#include <fc_list.h>

static
CXML_NODE *
new_cxml_node()
{
    CXML_NODE *ptr = malloc( sizeof(CXML_NODE) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML_NODE));
    ptr->attrs = new_fcl_list();
    return ptr;
}

CXML *
new_cxml()
{
    CXML *ptr = malloc( sizeof(CXML) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML));
    ptr->attrs = new_fcl_list();
    ptr->data  = new_fcl_list();
    return ptr;
}

CXML_ATTR *
new_cxml_attr()
{
    CXML_ATTR *ptr = malloc( sizeof(CXML_ATTR) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML_ATTR));
    return ptr;
}

int trash_cxml_node(void *v)
{
    CXML_NODE *d = v;
    if(!d) return FALSE;
    e_memfree(d->key);
    if (d->vtp == CXML_NTYPE )
    {
        fcl_list_destroy(d->val);
    }
    else
    {
        e_memfree( d->val );
    }
    fcl_list_destroy(d->attrs);
    e_memfree(d);
    return TRUE;
}

int trash_cxml_attr(void *v)
{
    if (!v) return FALSE;
    CXML_ATTR *ptr = v;
    e_memfree(ptr->key);
    e_memfree(ptr->val);
    e_memfree(ptr);
    return TRUE;
}

int trash_cxml(CXML *v)
{
    if(!v) return FALSE;
    e_memfree(v->tag);
    fcl_list_destroy(v->data);
    fcl_list_destroy(v->attrs);
    e_memfree(v);
    return TRUE;
}

int cxml_parse_attr(FCL_LIST *r, char *s, unsigned long long *i)
{
    if (!r || !s) {
        return FALSE;
    }
    
    unsigned long long p,
                       e,
                       q;
    CXML_ATTR         *t;
    
    /**
 * @brief NOTICE
 * Parse the attrs to FCL_LIST structure
 * p : Previous pos.
 * e : The `=` char pos
 * q : The `"` char mode or not.
 * b : The `>` char end mode
 * such as
 * `version="1.0" encoding="UTF-8"`
 */
    q = FALSE;
    p = *i;
    e = 0;
    
    for (; s[*i] != '\0'; (*i)++) {
        
        if ( s[*i] == '>' ) {
            *i += 1;
            break;
        }
        
        elif ( s[*i] == '=' ) {
            e = *i;
        }
        
        elif ( s[*i] == '"' ) {
            q = ~q;
            if ( !q && e ) {
                
                t = new_cxml_attr();
                t->key = e_substr(s + p, e - p);
                t->val = e_substr(s + e + 2, *i - e - 2);
                t->key_len =  e - p;
                t->val_len = *i - e - 2;
                fcl_list_push(r, t, trash_cxml_attr);
                
                (*i)++;
                E_SKIP_SPACE(s, *i);
                p = *i;
                (*i)--;
                e = 0;
            }
        }
    }
    
    return TRUE;
}

int cxml_parse_root(CXML *r, char *s, unsigned long long *i)
{
    if (!r || !s) {
        return FALSE;
    }
    
    unsigned long long p,
                       e,
                       q,
                       b;
    CXML_ATTR         *t;
/**
 * @brief NOTICE
 * Get tag name
 */
    p = *i;
    E_TO_CHAR(s, ' ', *i);
    r->tag = e_substr(s + p, *i - p);
    E_SKIP_SPACE(s, *i);
/**
 * @brief NOTICE
 * Parse the attrs to FCL_LIST structure
 * p : Previous pos.
 * e : The `=` char pos
 * q : The `"` char mode or not.
 * b : The `>` char end mode
 * such as
 * `version="1.0" encoding="UTF-8"`
 */
    cxml_parse_attr(r->attrs, s, i);

    return TRUE;
}

int cxml_check_key(char *key, unsigned long long len)
{
    unsigned long long i;
    
    if(!key) return FALSE;
    
    if ( e_isdigit(key[0]) || ( key[0] != '_' &&
                                e_notchar(key[0]) ) ) {
        return FALSE;
    }
    
    for ( i = 1; i < len; ++i )
    {
        if (e_ischar(key[i]) || key[i] == '.' || key[i] == '-' || key[i] == '_' )
            continue;
        else return FALSE;
    }
    return TRUE;
}

CXML *new_cxml_from_string(char *s, unsigned long long len)
{
    CXML              *rcxml;
    CXML_NODE         *cnode,
                      *tnode;
    FCS               *stack;
    unsigned long long i, a,
                       b, c;
    unsigned long         d;
/**
 * @brief NOTICE
 * i:  The string `s`'s current position
 */
    if ( !s || s[0] == '\0' ) {
        return NULL;
    }
    
    rcxml = new_cxml();
    if (!rcxml) return NULL;
    
    i   = 0;
    E_SKIP_SPACE(s, i);
    
    if ( s[i] == '<' && s[i+1] == '?' )
    {
/**
 * @brief NOTICE
 * Start with: '<?xml ' means the XML header
 * parsing the xml header info
 */
        s += 2;
        cxml_parse_root(rcxml, s, &i);
    }

/**
 * @brief NOTICE
 * Parsing the xml data node
 */
    b     = 0;
    stack = new_fcs();
    cnode = NULL;
    
/**
 * @brief NOTICE
 * a:   Used for to temp jump
 * b:   Used for the node's distance
 * Example:
 *  `<information><name>Josin</name><age><![CDATA[26]]></age><address>Shenzhen Guangdong</address></information>`
 */
    E_SKIP_SPACE(s, i);
    for ( ; i < len; ++i )
    {
        if ( s[i] == '<' && e_ischar(s[i+1]) )
        {
            cnode = new_cxml_node();
            fcs_push(stack, cnode, NULL);
            
            a = i;
            E_TO_TCHAR(s, " >", i);
            b = i;
            if ( s[i] == ' ' ) {
                i++;
                cxml_parse_attr(cnode->attrs, s, &i);
                i--;
            }
            cnode->key      = e_substr(s + a + 1, b - a - 1);
            cnode->key_len  = b - a - 1;
            if (!cxml_check_key(cnode->key, cnode->key_len)) {
                trash_cxml(rcxml);
                return NULL;
            }
            b = i + 1;
        }
        elif ( !b && s[i] == '<' && s[i+1] == '!' && s[i+2] == '-' && s[i+3] == '-' )
        {
/**
 * @brief NOTICE
 * XML comments
 */
            i += 4;
            for (; s[i] != '\0'; i++) {
                if (s[i] == '-' && s[i+1] == '-' && s[i+2] == '>')
                {
                    i += 2; break;
                }
            }
        }
        elif ( s[i] == '<' && s[i+1] == '/' )
        {
            a = i;
/**
 * @brief NOTICE
 * Current Node's String value
 */
            E_TO_CHAR(s, '>', i);
            if ( e_memcmp(cnode->key, s + a + 2, i - a - 2) != 0 )
            {
/**
 * @brief NOTICE
 * The node name isn't pair
 * printf("KEY: [%s] was unmatched!\n", cnode->key);
 */
                trash_cxml(rcxml);
                return NULL;
            }
    
/**
* @brief NOTICE
* if val is CDATA to prevent coding.
*/
            if ( b )
            {
                cnode->vtp = CXML_STYPE;
                if ( estr9cmp(s + b, "<![CDATA[") &&
                     s[a - 1] == '>' &&
                     s[a - 2] == ']' &&
                     s[a - 3] == ']'
                    ) {
                    c = b + 9;
                    /* cnode->val = e_substr(s + b + 9, a - b - 12); */
                    cnode->val_len = a - b  - 12;
                } else {
                    c = b;
                    /* cnode->val      = e_substr(s + b, a - b); */
                    cnode->val_len  = a - b;
                }
/**
 * @brief NOTICE
 * To delete the Comments data from the value
 */
                cnode->val = e_strip_xml_comments(s + c, cnode->val_len, &d);
                if ( !cnode->val ) {
                    trash_cxml(rcxml);
                    return NULL;
                }
                cnode->val_len = d;
            }
            
            if ( FCS_HEAD_P(stack) && FCN_NEXT_P(FCS_HEAD_P(stack)) )
            {
                cnode = FCN_DATA_P(FCN_NEXT_P(FCS_HEAD_P(stack)));
                if (!cnode->val)
                {
                    cnode->val = new_fcl_list();
                    cnode->vtp = CXML_NTYPE;
                }
                tnode = fcs_pop(stack);
                fcl_list_push(cnode->val, tnode, trash_cxml_node);
            }
            else
            {
                tnode = fcs_pop(stack);
                fcl_list_push(rcxml->data, tnode, trash_cxml_node);
            }
            
            b = 0;
        }
    }
    
/**
 * @brief NOTICE
 * To find is the multi root XML or not
 */
    if (FCL_LIST_NUM_P(rcxml->data) > 1) {
        rcxml->xtpe = CXML_MULTI_ROOT_TAG;
    }
    fcs_destroy(stack);
    return rcxml;
}

static
void _list_to_xml(FCL_LIST *v, CSTRING *res)
{
    if (!res || !v) {
        return ;
    }
    
    FCL_NODE    *fnode,
                *ffode;
    CXML_NODE   *cnode;
    CXML_ATTR   *cattr;
    
    FCL_LIST_FOREACH_HEAD(v, fnode) {
        
        cnode = FCL_NODE_DATA_P(fnode);
        if ( !cnode ) { continue; }
        
        new_cstring_add_string(res, E_STRL("<"));
        new_cstring_add_string( res, cnode->key, ( long )cnode->key_len );
        
        if ( cnode->attrs->num ) {
    
            new_cstring_add_string(res, E_STRL(" "));
            
            FCL_LIST_FOREACH_HEAD(cnode->attrs, ffode) {
                cattr = FCL_NODE_DATA_P(ffode);
                if (!cattr) {
                    continue;
                }
                new_cstring_add_string(res, cattr->key, cattr->key_len);
                new_cstring_add_string(res, E_STRL("=\""));
                new_cstring_add_string(res, cattr->val, cattr->val_len);
                new_cstring_add_string(res, E_STRL("\""));
                if ( FCL_NODE_NEXT_P(ffode) )
                {
                    new_cstring_add_string(res, E_STRL(" "));
                }
            } FCL_LIST_FOREACH_END();
        }
        new_cstring_add_string(res, E_STRL(">"));
        
        if ( cnode->vtp == CXML_NTYPE ) {
            _list_to_xml(cnode->val, res);
            
            new_cstring_add_string(res, E_STRL("</"));
            new_cstring_add_string( res, cnode->key, ( long )cnode->key_len );
            new_cstring_add_string(res, E_STRL(">"));
        }
        else {
            new_cstring_add_string(res, E_STRL("<![CDATA["));
            new_cstring_add_string( res, cnode->val, ( long )cnode->val_len );
            new_cstring_add_string(res, E_STRL("]]></"));
            new_cstring_add_string( res, cnode->key, ( long )cnode->key_len );
            new_cstring_add_string(res, E_STRL(">"));
        }
    } FCL_LIST_FOREACH_END();
}

char *new_string_from_cxml(CXML *c)
{
    if (!c) {
        return NULL;
    }
    
    char        *r;
    CSTRING     *res;
    FCL_NODE    *fnode;
    CXML_ATTR   *cattr;
    
    res = new_cstring();
    
    if ( c->attrs->num ) {
/**
 * @brief NOTICE
 * Generate XML header Line
 */
        new_cstring_add_string(res, E_STRL("<?"));
        new_cstring_add_string(res, c->tag, strlen(c->tag));
        new_cstring_add_string(res, E_STRL(" "));
        
        FCL_LIST_FOREACH_HEAD(c->attrs, fnode) {
            cattr = FCL_NODE_DATA_P(fnode);
            if (!cattr) {
                continue;
            }
            new_cstring_add_string(res, cattr->key, cattr->key_len);
            new_cstring_add_string(res, E_STRL("=\""));
            new_cstring_add_string(res, cattr->val, cattr->val_len);
            new_cstring_add_string(res, E_STRL("\""));
            if ( FCL_NODE_NEXT_P(fnode) ) {
                new_cstring_add_string(res, E_STRL(" "));
            }
        } FCL_LIST_FOREACH_END();
        new_cstring_add_string(res, E_STRL("?>"));
    }
    
/**
 * @brief NOTICE
 * Generate the data list
 */
    if (c->data) {
      _list_to_xml(c->data, res);
    }

    r = res->s;
    e_memfree(res);
    return r;
}